/*
arduino2tmsg

Licensed under the MIT License <http://opensource.org/licenses/MIT>.
SPDX-License-Identifier: MIT
Copyright (c) 2018 zjzengdongyang <http://zjzdy.cn>.

Permission is hereby  granted, free of charge, to any  person obtaining a copy
of this software and associated  documentation files (the "Software"), to deal
in the Software  without restriction, including without  limitation the rights
to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <iostream>
#include <streambuf>
#include <fstream>
#include <thread>
#include <map>
#include <chrono>
#include <cstring>
#include <cstdint>
#include <json.hpp>
#define ASIO_STANDALONE 1 //纯asio库, 不引入boost
#include <asio.hpp>
#include "tmsg.hpp"

using namespace std;
using namespace asio;
using namespace nlohmann;
using namespace std;
using namespace std::chrono;

//同步超时读取
struct readTimeoutStatus {
    bool isFinish;//该次读取是否完成
    size_t bytes_transferred;//传输的数据大小
};
/**
 * 读取数据直到缓冲区满,或超时,或出现错误
 * @param sp 串口
 * @param buf 缓冲区
 * @param wait_time 超时时间
 * @return 读取到的数据大小
 */
template<typename Rep, typename Period>
size_t readTimeout(serial_port &sp, mutable_buffer &buf, const std::chrono::duration<Rep, Period>& wait_time) {
    readTimeoutStatus ts = {false,0};//设置状态
    auto start = steady_clock::now();//记录当前时间
    async_read(sp, buf, [&ts] (error_code ec, size_t bytes_transferred) { ts = {true,bytes_transferred}; });//读取数据
    sp.get_io_service().reset();//重置io_service对象，为随后的run调用做准备
    while(!ts.isFinish && (steady_clock::now()-start < wait_time)) {
        auto newWaitTime = (milliseconds(10) > (wait_time-(steady_clock::now()-start)) ? milliseconds(10) : (wait_time-(steady_clock::now()-start)));//计算新的超时时间,最小10ms
        sp.get_io_service().run_one_for(newWaitTime);//让异步读取正常运行,
    }
    if(!ts.isFinish) {
        sp.cancel();//取消异步读取
        sp.get_io_service().run_one();//让其做可能存在的收尾工作
    }
    this_thread::yield();//让步给可能存在的其他线程
    return ts.bytes_transferred;
}

/**
 * 读取数据直到遇到停止符,或超时,或出现错误
 * 缓冲区内在停止符后仍然可能存在已读取的数据,请处理完本次数据后继续使用该缓冲区以读取后面的数据,否则后面的已读取的数据则被丢弃
 * @param sp 串口
 * @param buf 流缓冲区
 * @param delim 停止符
 * @param wait_time 超时时间
 * @return 读取到的数据大小,包括停止符在内
 */
template<typename Rep, typename Period>
size_t readTimeout_until(serial_port &sp, asio::streambuf &buf, char delim, const std::chrono::duration<Rep, Period>& wait_time) {
    readTimeoutStatus ts = {false,0};
    auto start = steady_clock::now();
    async_read_until(sp, buf, delim, [&ts] (error_code ec, size_t bytes_transferred) { ts = {true,bytes_transferred}; });
    sp.get_io_service().reset();
    while(!ts.isFinish && (steady_clock::now()-start < wait_time)) {
        auto newWaitTime = (milliseconds(10) > (wait_time-(steady_clock::now()-start)) ? milliseconds(10) : (wait_time-(steady_clock::now()-start)));
        sp.get_io_service().run_one_for(newWaitTime);
    }
    if(!ts.isFinish) {
        sp.cancel();
        sp.get_io_service().run_one();
    }
    this_thread::yield();
    return ts.bytes_transferred;
}
/**
 * 读取数据帧, 负责检查数据有效性, 但可能因为缓冲区大小不足而无法完全返回
 * @param sp 串口
 * @param buf 数据缓冲区
 * @param device_id 设备id
 * @param wait_time 超时时间
 * @return 读取到的数据大小
 */
template<typename Rep, typename Period>
size_t readDataFrame(serial_port &sp, mutable_buffer &buf, unsigned int device_id, const std::chrono::duration<Rep, Period>& wait_time) {
    asio::streambuf sb;//流缓冲区
    basic_string<unsigned char, char_traits<unsigned char>, allocator<unsigned char>> data;//存放数据
    auto start = steady_clock::now();//记录当前时间,以计算是否超时
    size_t bytes_transferred = readTimeout_until(sp, sb, 0x1D, wait_time);//第一次读取数据
    //必须拷贝consume部分的数据,尽管consume不会令已读取的数据无效,但异步读取数据时,会调用prepare,让已读取的数据部分内存空间被后面的数据覆盖
    data.append((unsigned char*)sb.data().data(), bytes_transferred);
    //必须consume,否则下次读取数据还是会再读一遍这次的数据,在这次停止的地方又停止
    sb.consume(bytes_transferred);
    if(bytes_transferred < 5) {//帧长最小为5, 同时确保已经读取到数据长度
        auto newWaitTime = (milliseconds(10) > (wait_time-(steady_clock::now()-start)) ? milliseconds(10) : (wait_time-(steady_clock::now()-start)));//计算新的超时时间,最小10ms
        bytes_transferred += readTimeout_until(sp, sb, 0x1D, newWaitTime);
        data.append((unsigned char*)sb.data().data(), bytes_transferred);
        sb.consume(bytes_transferred);
    }
    if(bytes_transferred > 1 && data[0] == 0xEA && data[1] == device_id) {//检查帧头
        //读取数据到达定义的长度,或者超时
        while(bytes_transferred != data[2]+5 && (steady_clock::now()-start < wait_time)) {
            auto newWaitTime = (milliseconds(10) > (wait_time-(steady_clock::now()-start)) ? milliseconds(10) : (wait_time-(steady_clock::now()-start)));
            bytes_transferred += readTimeout_until(sp, sb, 0x1D, newWaitTime);
            data.append((unsigned char*)sb.data().data(), bytes_transferred);
            sb.consume(bytes_transferred);
        }
        if(bytes_transferred == data[2]+5) {
            //计算效验和
            unsigned char sum = 0;
            for(int i = 3; i < bytes_transferred-2; i++)
                sum += data[i];
            if(sum == data[bytes_transferred-2]) {
                memcpy(buf.data(), data.data(), min(buf.size(), bytes_transferred));//拷贝数据到缓冲区
                return min(buf.size(), bytes_transferred);
            }
        }
    }
    return 0;
}

int main(int argc, char* argv[])
{
    if(argc < 3 || string(argv[1]).find('h') < 3) {//无输入, 或第一个参数为-h,--h,--help,help
        cout << "Usage: " << argv[0] << " SerialPort DataDefineJSON [MQTT Server URI]" << endl;
        return 0;
    }
    string serverURI;
    if(argc > 3)
        serverURI = argv[3];
    if(serverURI.empty())
        serverURI = "tcp://localhost:1883";
    tmsg tm(serverURI);//初始化tmsg
    //连接服务器
    if(!tm.connect()) {
        cout << "Connect fail." << endl;
        return -1;
    }
    io_service io_s;
    serial_port sp(io_s , argv[1]);
    //设置串口参数
    sp.set_option(serial_port::baud_rate(115200));
    sp.set_option(serial_port::flow_control());
    sp.set_option(serial_port::parity());
    sp.set_option(serial_port::stop_bits());
    sp.set_option(serial_port::character_size(8));
    if(!sp.is_open()) {
        cout << "Open serial port fail." << endl;
        return -1;
    }
    //读取配置信息
    ifstream defineJSON(argv[2]);
    auto define = json::parse(string((istreambuf_iterator<char>(defineJSON)), istreambuf_iterator<char>())).get<map<string, string>>();
    if(define.find("topic") == define.end() || define["topic"].empty()) {
        cout << "Can't find a valid topic info." << endl;
        return -1;
    }
    string topic = define["topic"];
    //建立缓冲区
    unsigned char cache[256] = {0};
    auto buf = buffer(cache, 256);
    //读取设备id
    while(cache[0] != 0xEE || cache[2] != 0x1D) {
        readTimeout(sp, buf, milliseconds(500));
        write(sp, buffer(string("0"), 1));//请求id
        auto buf2 = buffer(cache, 3);
        readTimeout(sp, buf2, seconds(2));
    }
    unsigned int device_id = cache[1];
    cout << "Device ID: " << device_id << endl;
    //读取,应用配置
    if(define["topicWithID"] == "true")
        topic += to_string(device_id);
    bool retain = (define["retain"] == "true");
    int qos = atoi(define["qos"].c_str());
    auto data_start = define.find("data");
    if(data_start == define.end()) {
        cout << "Can't find a data start mark." << endl;
        return -1;
    }
    cout << "Topic " << topic << endl;
    //读取数据转发
    while(1) {
        tmsg::msg m;
        write(sp, buffer("1", 1));//请求数据
        auto len = readDataFrame(sp, buf, device_id, seconds(1));//读取数据
        cout << "Get Data Len " << len << endl;
        if(len > 5 && len == cache[2]+5 && cache[len-1] == 0x1D) {//确定大小与完整
            size_t offset = 3;
            for(auto beg = data_start; beg != define.end(); beg++) {
                if(beg->second == "uint8_t") {
                    uint8_t t;
                    memcpy(&t, cache+offset, 1);
                    offset += 1;
                    m.pack(beg->first,t);
                } else if(beg->second == "int8_t") {
                    int8_t t;
                    memcpy(&t, cache+offset, 1);
                    offset += 1;
                    m.pack(beg->first,t);
                } else if(beg->second == "uint16_t") {
                    uint16_t t;
                    memcpy(&t, cache+offset, 2);
                    offset += 2;
                    m.pack(beg->first,t);
                } else if(beg->second == "int16_t") {
                    int16_t t;
                    memcpy(&t, cache+offset, 2);
                    offset += 2;
                    m.pack(beg->first,t);
                } else if(beg->second == "uint32_t") {
                    uint32_t t;
                    memcpy(&t, cache+offset, 4);
                    offset += 4;
                    m.pack(beg->first,t);
                } else if(beg->second == "int32_t") {
                    int32_t t;
                    memcpy(&t, cache+offset, 4);
                    offset += 4;
                    m.pack(beg->first,t);
                } else if(beg->second == "uint64_t") {
                    uint64_t t;
                    memcpy(&t, cache+offset, 8);
                    offset += 8;
                    m.pack(beg->first,t);
                } else if(beg->second == "int64_t") {
                    int64_t t;
                    memcpy(&t, cache+offset, 8);
                    offset += 8;
                    m.pack(beg->first,t);
                } else if(beg->second == "float") {
                    float t;
                    memcpy(&t, cache+offset, 4);
                    offset += 4;
                    m.pack(beg->first,t);
                } else if(beg->second == "double") {
                    double t;
                    memcpy(&t, cache+offset, 8);
                    offset += 8;
                    m.pack(beg->first,t);
                } else if(beg->second == "string") {
                    string t;
                    unsigned char size;
                    memcpy(&size, cache+offset, 1);
                    offset += 1;
                    t.append((char*)cache+offset, size);
                    offset += size;
                    m.pack(beg->first,t);
                }
            }
            tm.publish(topic, m, qos, retain);
        } else
            readTimeout(sp, buf, milliseconds(300));//读取数据失败时的等待并清空数据
    }
    return 0;
}